﻿open System.IO
open System.Reflection
open System.Windows
open System.Windows.Controls
open System.Windows.Markup
open SharpCompress.Archive
open SharpCompress.Archive.Zip
open SharpCompress.Archive.Rar

let isImage (fn:string) =
  let f = new FileInfo(fn)
  match f.Extension.ToUpper() with
  | ".JPG" | ".JPEG" | ".PNG" -> true
  | _ -> false

type ComicBook(fn:string) =
  let archive = if ZipArchive.IsZipFile(fn) then ZipArchive.Open(fn) :> IArchive else RarArchive.Open(fn) :> IArchive
  let items = archive.Entries |> Seq.filter (fun v -> isImage(v.FilePath)) |> Seq.sortBy (fun v -> v.FilePath) |>  Seq.toArray
  let count = items.Length
  let cache = Array.init count (fun _ -> new System.WeakReference(null))
  let read buffer idx =
    use s = items.[idx].OpenEntryStream()
    s.CopyTo(buffer)

  let getPage idx =
    if not(cache.[idx].IsAlive) then
      let buffer = new MemoryStream()
      read buffer idx |> ignore
      buffer.Seek(0L, SeekOrigin.Begin) |> ignore
      let bmp = Media.Imaging.BitmapImage()
      bmp.BeginInit()
      bmp.StreamSource <- buffer
      bmp.EndInit()
      cache.[idx].Target <- bmp

    cache.[idx].Target :?> Media.Imaging.BitmapImage

  member x.PageNames () = seq { for i = 0 to count - 1 do yield (items.[i].FilePath) }
  member x.Count with get() = count

  member x.Item item = getPage item

type MoveDir = Abs of int | Rel of int

  
[<System.STAThread>]
[<EntryPoint>]
let main(args) =
    let data = System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData) + @"\ComicsDisplay\bookmarks.xml"
    let bookmarks = if not(File.Exists(data)) then
                      let fi = new FileInfo(data)
                      if not(Directory.Exists(fi.DirectoryName)) then Directory.CreateDirectory(fi.DirectoryName) |> ignore
                      let b = new ComicsDisplay.Bookmarks()
                      b.Save(data)
                      b
                    else
                      ComicsDisplay.Bookmarks.Load(data)

    let book = new ComicBook(args.[0])
    let current = ref 0
    let w = Application.LoadComponent(new System.Uri("/ComicsDisplay;component/MainWindow.xaml", System.UriKind.Relative)) :?> Window
    let display = w.FindName("Display") :?> StackPanel
    let sz, mid = 9, 4
    let imgs = Array.init sz (fun idx ->
      let scale = 1. / float(abs(idx - mid) + 1)
      let im = new Image()
      im.LayoutTransform <- new Media.ScaleTransform(ScaleX=scale, ScaleY=scale)
      if scale <> 1. then
        im.Opacity <- scale - 0.1
      let b = new System.Windows.Data.Binding()
      b.ElementName <- "this"
      b.Path <- new PropertyPath("ActualHeight")
      im.SetBinding(Image.HeightProperty, b) |> ignore
      im.Margin <- new Thickness(10., 0., 10., 0.)
      im
    )

    imgs |> Array.iter (fun im -> display.Children.Add(im) |> ignore)
    let img = imgs.[mid]
    let tt = new Media.TranslateTransform()
    let scale = img.LayoutTransform :?> Media.ScaleTransform 
    let setScale v = scale.ScaleX <- v; scale.ScaleY <- v
    img.Stretch <- Media.Stretch.UniformToFill
    img.StretchDirection <- StretchDirection.DownOnly
    display.MouseWheel.Add(fun evt ->
      max 1. (scale.ScaleX * (1. + float(System.Math.Sign(evt.Delta)) * 0.02)) |> setScale
    )

    let progress = w.FindName("Progress") :?> Shapes.Rectangle

    let move d =
      let setCurrent v = current := max 0 (min (book.Count - 1) v)
      match d with 
      | Abs v -> setCurrent v 
      | Rel v -> setCurrent(!current + v)

      imgs |> Array.iteri (fun idx v -> 
        let i = !current + idx - mid
        if i < 0 || i >= book.Count then v.Source <- null
        else v.Source <- book.[i]
      )
      progress.Width <- (float(!current) / float(book.Count)) * w.ActualWidth
    
    img.LayoutUpdated.Add(fun _ ->
      let t = img.TransformToAncestor(w)
      let p1 = t.Transform(new Point(0., 0.))
      let p2 = t.Transform(new Point(img.ActualWidth, 0.))
      let tt = display.RenderTransform :?> Media.TranslateTransform
      tt.X <- tt.X + ((w.ActualWidth - (p2.X - p1.X)) / 2.) - p1.X
    )

    w.Loaded.Add(fun _ -> 
#if DEBUG
#else
      w.Topmost <- true
#endif
      if bookmarks.comics.ContainsKey(args.[0]) then
        current := bookmarks.comics.[args.[0]]
      move(Abs !current)
    )
    w.Closed.Add(fun _ ->
        bookmarks.comics.[args.[0]] <- !current
        bookmarks.Save(data)
    )

    let dragstart = ref(Point())
    let yoff = ref 0.

    w.MouseDown.Add(fun evt -> 
      dragstart := evt.MouseDevice.GetPosition(w)
      yoff := (display.RenderTransform :?> Media.TranslateTransform).Y
    )

    w.MouseMove.Add(fun evt ->
      match evt.LeftButton with
      | Input.MouseButtonState.Pressed ->
        let tt = display.RenderTransform :?> Media.TranslateTransform
        let p = evt.MouseDevice.GetPosition(w)
        tt.Y <- !yoff + p.Y - (!dragstart).Y
      | _ -> ()
    )
    w.KeyDown.Add(fun evt ->
      match evt.Key with
      | Input.Key.Back | Input.Key.BrowserBack | Input.Key.Left -> move(Rel -1)
      | Input.Key.BrowserForward | Input.Key.Space | Input.Key.Right -> move(Rel 1)
      | Input.Key.Home -> move(Abs 0)
      | Input.Key.End -> move(Abs (book.Count - 1))
      | Input.Key.Escape -> w.Close()
      | _ -> ()
    )
    (new Application()).Run(w)
